/* Copyright (c) 2001-2008, The HSQL Development Group
 * All rights reserved.
 *
 * Redistribution and use _in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions _in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer _in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the HSQL Development Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


using System;
using System.IO; 
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Collections.Generic;
using System.Text;
using System.Security.Cryptography.X509Certificates;
using EffiProz.Core;
using EffiProz.Core.Lib;
using EffiProz.Core.Persist;
using EffiProz.Core.Resources;
using EffiProz.Core.Results;
using EffiProz.Core.Errors;
using EffiProz.Core.Server;


// fredt@users 20020215 - patch 1.7.0
// methods reorganised to use new HsqlProperties class
// fredt@users 20020424 - patch 1.7.0 - shutdown without exit
// see the comments _in ServerConnection.java
// unsaved@users 20021113 - patch 1.7.2 - SSL support
// boucherb@users 20030510-14 - 1.7.2 - SSL support moved to factory interface
// boucherb@users 20030510-14 - 1.7.2 - service control, JavaBean API
// fredt@users 20030916 - 1.7.2 - review, simplification and multiple DB's
// fredt@users 20040320 - 1.7.2 - review and correction
// fredt@users 20050225 - 1.8.0 - minor corrections
// fredt@users 20051231 - 1.8.1 - support for remote opening of databases
// fredt@users 20080531 - 1.8.1 - removed synchronized from print methods
// unnecessary and could cause deadlock

/**
 * The HSQLDB HSQL protocol network database server. <p>
 *
 * A Server object acts as a network database server and is one way of using
 * the client-server mode of HSQLDB Database Engine. Instances of this
 * class handle native HSQL protocol connections exclusively, allowing database
 * queries to be performed efficienly across the network.  Server's direct
 * descendent, WebServer, handles HTTP protocol connections exclusively,
 * allowing HSQL protocol to be tunneled over HTTP to avoid sandbox and
 * firewall issues, albeit less efficiently. <p>
 *
 * There are a number of ways to configure and start a Server instance. <p>
 *
 * When started from the command line or programatically via the main(String[])
 * method, configuration occurs _in three phases, with later phases overriding
 * properties set by previous phases:
 *
 * <ol>
 *   <li>Upon construction, a Server object is assigned a set of default
 *       properties. <p>
 *
 *   <li>If it exists, properties are loaded from a file named
 *       'server.properties' _in the present working directory. <p>
 *
 *   <li>The command line arguments (alternatively, the String[] passed to
 *       main()) are parsed and used to further configure the Server's
 *       properties. <p>
 *
 * </ol> <p>
 *
 * From the command line, the options are as follows: <p>
 * <pre>
 * +----------------+-------------+----------+------------------------------+
 * |    OPTION      |    TYPE     | DEFAULT  |         DESCRIPTION          |
 * +----------------+-------------+----------+------------------------------|
 * | -?             | --          | --       | prints this message          |
 * | -address       | name|number | any      | server inet address          |
 * | -port          | number      | 9001/544 | port at which server listens |
 * | -database.i    | [type]spec  | 0=test   | path of database i           |
 * | -dbname.i      | alias       | --       | url alias for database i     |
 * | -silent        | true|false  | true     | false => display all queries |
 * | -trace         | true|false  | false    | display JDBC trace messages  |
 * | -tls           | true|false  | false    | TLS/SSL (secure) sockets     |
 * | -no_system_exit| true|false  | false    | do not issue System.exit()   |
 * | -remote_open   | true|false  | false    | can open databases remotely  |
 * +----------------+-------------+----------+------------------------------+
 * </pre>
 *
 * The <em>database.i</em> and <em>dbname.i</em> options need further
 * explanation:
 *
 * <ul>
 *   <li>Multiple databases can be served by each instance of the Server.
 *       The value of <em>i</em> is currently limited to the range 0..9,
 *       allowing up to 10 different databases. Any number is this range
 *       can be used.<p>
 *
 *   <li>The value assigned to <em>database.i</em> is interpreted using the
 *       format <b>'[type]spec'</b>, where the optional <em>type</em> component
 *       is one of <b>'file:'</b>, <b>'res:'</b> or <b>'mem:'</b> and the
 *       <em>spec</em> component is interpreted _in the context of the
 *       <em>type</em> component.  <p>
 *
 *       If omitted, the <em>type</em> component is taken to be
 *       <b>'file:'</b>.  <p>
 *
 *        A full description of how
 *       <b>'[type]spec'</b> values are interpreted appears _in the overview for
 *       {@link org.hsqldb.jdbc.jdbcConnection jdbcConnection}. <p>
 *
 *   <li>The value assigned to <em>dbname.i</em> is taken to be the key used to
 *       look up the desired database instance and thus corresponds to the
 *       <b>&lt;alias&gt;</b> component of the HSQLDB HSQL protocol database
 *       connection url:
 *       'jdbc:hsqldb:hsql[s]://host[port][/<b>&lt;alias&gt;</b>]'. <p>
 *
 *   <li>The value of <em>database.0</em> is special. If  <em>dbname.0</em>
 *       is not specified, then this defaults to an empty string and
 *       a connection is made to <em>database.0</em> path when
 *       the <b>&lt;alias&gt;</b> component of an HSQLDB HSQL protocol database
 *       connection url is omitted. If a <em>database</em> key/value pair is
 *       found _in the properties when the main method is called, this
 *       pair is supersedes the <em>database.0</em> setting<p>
 *
 *       This behaviour allows the previous
 *       database connection url format to work with essentially unchanged
 *       semantics.<p>
 *
 *   <li>When the  <em>remote_open</em> property is true, a connection attempt
 *       to an unopened database results _in the database being opened. The URL
 *       for connection should include the property filepath to specify the path.
 *       'jdbc:hsqldb:hsql[s]://host[port]/<b>&lt;alias&gt;;filepath=hsqldb:file:&lt;database path&gt;</b>'.
 *       the given alias and filepath value will be associated together. The
 *       database user and password to start this connection must be valid.
 *       If this form of connection is used again, after the database has been
 *       opened, the filepath property is ignored.<p>
 *
 *   <li>Once an alias such as "mydb" has been associated with a path, it cannot
 *       be  reassigned to a different path.<p>
 *
 *   <li>If a database is closed with the SHUTDOWN command, its
 *       alias is removed. It is then possible to connect to this database again
 *       with a different (or the same) alias.<p>
 *
 *   <li>If the same database is connected to via two different
 *       aliases, and then one of the is closed with the SHUTDOWN command, the
 *       other is also closed.<p>
 * </ul>
 *
 * From the 'server.properties' file, options can be set similarly, using a
 * slightly different format. <p>
 *
 * Here is an example 'server.properties' file:
 *
 * <pre>
 * server.port=9001
 * server.database.0=test
 * server.dbname.0=...
 * ...
 * server.database.n=...
 * server.dbname.n=...
 * server.silent=true
 * </pre>
 *
 * Starting with 1.7.2, Server has been refactored to become a simple JavaBean
 * with non-blocking start() and stop() service methods.  It is possible to
 * configure a Server instance through the JavaBean API as well, but this
 * part of the public interface is still under review and will not be finalized
 * or documented fully until the const 1.7.2 release. <p>
 *
 * <b>Note:</b> <p>
 *
 * The 'no_system_exit' property is of particular interest. <p>
 *
 * If a Server instance is to run embedded in, say, an application server,
 * such as when the jdbcDataSource or HsqlServerFactory classes are used, it
 * is typically necessary to avoid calling System.exit() when the Server
 * instance shuts down. <p>
 *
 * By default, 'no_system_exit' is set: <p>
 *
 * <ol>
 *    <li><b>true</b> when a Server is started directly from the start()
 *        method. <p>
 *
 *    <li><b>false</b> when a Server is started from the main(String[])
 *         method.
 * </ol> <p>
 *
 * These values are natural to their context because the first case allows
 * the JVM to exit by default on Server shutdown when a Server instance is
 * started from a command line environment, whereas the second case prevents
 * a typically unwanted JVM exit on Server shutdown when a Server intance
 * is started as part of a larger framework. <p>
 *
 * Replaces original Hypersonic source of the same name.
 *
 * @author fredt@users
 * @version 1.8.0
 * @since 1.7.2
 *
 * @jmx.mbean
 *    description="HSQLDB Server"
 *    extends="org.hsqldb.mx.mbean.RegistrationSupportBaseMBean"
 *
 * @jboss.xmbean
 */
namespace EffiProz.Server
{

    public class Server : IServer
    {
        protected static  int serverBundleHandle =
        BundleHandler.getBundleHandle2("EffiProz.Server.Resources.Server_messages",
        typeof(Server).Assembly);

        //
        public EffiProzProperties serverProperties;

        //
        public HashSet<ServerConnection> serverConnSet;

        //
        private String[] dbAlias;
        private String[] dbType;
        private String[] dbPath;
        private EffiProzProperties[] dbProps;
        internal int[] dbID;
        protected long[] dbActionSequence;

        //  Currently unused
        private int maxConnections;
        volatile int actionSequence =0;

        // set of aliases
        HashSet aliasSet = new HashSet();

        //
        protected string serverId;
        protected int serverProtocol;
        protected List<Thread>  serverConnectionThreadGroup;
        protected TcpListener listner;

        //
        private Thread serverThread;
        private Exception serverError;
        private volatile int serverState;
        private volatile bool _isSilent;
        private volatile bool isRemoteOpen;
        private TextWriter      logWriter;
        private TextWriter      errWriter;

        public X509Certificate ServerCertificate
        {
            get;
            set;
        }

        //

        /**
         * A specialized Thread inner class _in which the run() method of this
         * server executes.
         */

        /**
         * Executes the run() method of this server
         */
        public void ServerThreadRun() {
            this.run();
            printWithThread("ServerThread.run() exited");
        }
  

        /**
         * Creates a new Server instance handling HSQL protocol connections.
         */
        public Server()
            : this(ServerConstants.SC_PROTOCOL_EFZ)
        {

        }

        /**
         * Creates a new Server instance handling the specified connection
         * protocol. <p>
         *
         * For example, the no-args WebServer constructor invokes this constructor
         * with ServerConstants.SC_PROTOCOL_HTTP, while the Server() no args
         * contructor invokes this constructor with
         * ServerConstants.SC_PROTOCOL_HSQL. <p>
         *
         * @param protocol the ServerConstants code indicating which
         *      connection protocol to handle
         */
        protected Server(int protocol)
        {
            init(protocol);
        }


        public Server(int protocol, X509Certificate serverCertificate)
        {
            init(protocol);
            serverProperties.setProperty(ServerConstants.SC_KEY_TLS, true);
            ServerCertificate = serverCertificate;
        }


        public Server(X509Certificate serverCertificate) : 
            this(ServerConstants.SC_PROTOCOL_EFZ,serverCertificate)
        {          
        }


        /**
         * Checks if this Server object is or is not running and throws if the
         * current state does not match the specified value.
         *
         * @param running if true, ensure the server is running, else ensure the
         *      server is not running
         * @  if the supplied value does not match the
         *      current running status
         */
        public void checkRunning(bool running)
        {

            int state;
            bool error;

            printWithThread("checkRunning(" + running + ") entered");

            state = getState();
            error = (running && state != ServerConstants.SERVER_STATE_ONLINE)
                    || (!running
                        && state != ServerConstants.SERVER_STATE_SHUTDOWN);

            if (error)
            {
                string msg = "server is " + (running ? "not "
                                                     : "") + "running";

                throw new Exception(msg);
            }

            printWithThread("checkRunning(" + running + ") exited");
        }

            /**
             * Closes all connections to this Server.
             *
             * @jmx.managed-operation
             *  impact="ACTION"
             *  description="Closes all open connections"
             */
            public  void signalCloseAllServerConnections() {

                lock (this)
                {
                    printWithThread("signalCloseAllServerConnections() entered");

                    lock (serverConnSet)
                    {

                        foreach (var sc in serverConnSet)
                        {


                            printWithThread("Closing " + sc);

                            // also removes all but one connection from serverConnSet
                            sc.signalClose();
                        }
                    }
                
                    printWithThread("signalCloseAllServerConnections() exited");
                }
            }

            protected void finalize()  {

                if (serverThread != null) {
                    releaseServerSocket();
                }
            }

        /**
         * Retrieves, _in string form, this server's host address.
         *
         * @return this server's host address
         *
         * @jmx.managed-attribute
         *  access="read-write"
         *  description="Host InetAddress"
         */
        public string getAddress()
        {

            return listner == null
                   ? serverProperties.getProperty(ServerConstants.SC_KEY_ADDRESS)
                   :  ((IPEndPoint)listner.LocalEndpoint).Address.ToString();
        }

            /**
             * Retrieves the url alias (network name) of the i'th database
             * that this Server hosts.
             *
             * @param index the index of the url alias upon which to report
             * @param asconfigured if true, report the configured value, else
             *      the live value
             * @return the url alias component of the i'th database
             *      that this Server hosts, or null if no such name exists.
             *
             * @jmx.managed-operation
             *  impact="INFO"
             *  description="url alias component of the i'th hosted Database"
             *
             * @jmx.managed-operation-parameter
             *      name="index"
             *      type="int"
             *      position="0"
             *      description="This Server's index for the hosted Database"
             *
             * @jmx.managed-operation-parameter
             *      name="asconfigured"
             *      type="bool"
             *      position="1"
             *      description="if true, the configured value, else the live value"
             */
            public string getDatabaseName(int index, bool asconfigured) {

                if (asconfigured) {
                    return serverProperties.getProperty(ServerConstants.SC_KEY_DBNAME
                                                        + "." + index);
                } else if (getState() == ServerConstants.SERVER_STATE_ONLINE) {
                    return (dbAlias == null || index < 0 || index >= dbAlias.Length)
                           ? null
                           : dbAlias[index];
                } else {
                    return null;
                }
            }

        /**
         * Retrieves the HSQLDB path descriptor (uri) of the i'th
         * Database that this Server hosts.
         *
         * @param index the index of the uri upon which to report
         * @param asconfigured if true, report the configured value, else
         *      the live value
         * @return the HSQLDB database path descriptor of the i'th database
         *      that this Server hosts, or null if no such path descriptor
         *      exists
         *
         * @jmx.managed-operation
         *  impact="INFO"
         *  description="For i'th hosted database"
         *
         * @jmx.managed-operation-parameter
         *      name="index"
         *      type="int"
         *      position="0"
         *      description="This Server's index for the hosted Database"
         *
         * @jmx.managed-operation-parameter
         *      name="asconfigured"
         *      type="bool"
         *      position="1"
         *      description="if true, the configured value, else the live value"
         */
        public string getDatabasePath(int index, bool asconfigured)
        {

            if (asconfigured)
            {
                return serverProperties.getProperty(ServerConstants.SC_KEY_DATABASE
                                                    + "." + index);
            }
            else if (getState() == ServerConstants.SERVER_STATE_ONLINE)
            {
                return (dbPath == null || index < 0 || index >= dbPath.Length)
                       ? null
                       : dbPath[index];
            }
            else
            {
                return null;
            }
        }

        public string getDatabaseType(int index)
        {
            return (dbType == null || index < 0 || index >= dbType.Length) ? null
                                                                           : dbType[index];
        }

            /**
             * Retrieves the name of the web page served when no page is specified.
             * This attribute is relevant only when server protocol is HTTP(S).
             *
             * @return the name of the web page served when no page is specified
             *
             * @jmx.managed-attribute
             *  access="read-write"
             *  description="Used when server protocol is HTTP(S)"
             */
            public virtual string getDefaultWebPage() {
                return "[IGNORED]";
            }

            /**
             * Retrieves a string object describing the command line and
             * properties options for this Server.
             *
             * @return the command line and properties options help for this Server
             */
            public virtual string getHelpString() {
                return BundleHandler.getString(serverBundleHandle, "server.help");
            }

        /**
         * Retrieves the PrintWriter to which server errors are printed.
         *
         * @return the PrintWriter to which server errors are printed.
         */
        public TextWriter getErrWriter()
        {
            return errWriter;
        }

        /**
         * Retrieves the PrintWriter to which server messages are printed.
         *
         * @return the PrintWriter to which server messages are printed.
         */
        public TextWriter getLogWriter()
        {
            return logWriter;
        }

        /**
         * Retrieves this server's host port.
         *
         * @return this server's host port
         *
         * @jmx.managed-attribute
         *  access="read-write"
         *  description="At which ServerSocket listens for connections"
         */
        public int getPort()
        {
            return serverProperties.getIntegerProperty(ServerConstants.SC_KEY_PORT,
                    ServerConfiguration.getDefaultPort(serverProtocol, isTls()));
        }

        /**
         * Retrieves this server's product name.  <p>
         *
         * Typically, this will be something like: "HSQLDB xxx server".
         *
         * @return the product name of this server
         *
         * @jmx.managed-attribute
         *  access="read-only"
         *  description="Of Server"
         */
        public virtual string getProductName()
        {
            return "EffiProz server";
        }

        /**
         * Retrieves the server's product version, as a String.  <p>
         *
         * Typically, this will be something like: "1.x.x" or "2.x.x" and so on.
         *
         * @return the product version of the server
         *
         * @jmx.managed-attribute
         *  access="read-only"
         *  description="Of Server"
         */
        public string getProductVersion()
        {
            return "0.1.0";
        }

            /**
             * Retrieves a string respresentaion of the network protocol
             * this server offers, typically one of 'HTTP', HTTPS', 'HSQL' or 'HSQLS'.
             *
             * @return string respresentation of this server's protocol
             *
             * @jmx.managed-attribute
             *  access="read-only"
             *  description="Used to handle connections"
             */
            public virtual string getProtocol() {
                return isTls() ? "EFZS"
                               : "EFZ";
            }

        /**
         * Retrieves a Exception indicating the last server error, if any. <p>
         *
         * @return a Exception indicating the last server error
         *
         * @jmx.managed-attribute
         *  access="read-only"
         *  description="Indicating last exception state"
         */
        public Exception getServerError()
        {
            return serverError;
        }

            /**
             * Retrieves a string identifying this Server object.
             *
             * @return a string identifying this Server object
             *
             * @jmx.managed-attribute
             *  access="read-only"
             *  description="Identifying Server"
             */
            public string getServerId() {
                return serverId;
            }

        /**
         * Retrieves current state of this server _in numerically coded form. <p>
         *
         * Typically, this will be one of: <p>
         *
         * <ol>
         * <li>ServerProperties.SERVER_STATE_ONLINE (1)
         * <li>ServerProperties.SERVER_STATE_OPENING (4)
         * <li>ServerProperties.SERVER_STATE_CLOSING (8)
         * <li>ServerProperties.SERVER_STATE_SHUTDOWN (16)
         * </ol>
         *
         * @return this server's state code.
         *
         * @jmx.managed-attribute
         *  access="read-only"
         *  description="1:ONLINE 4:OPENING 8:CLOSING, 16:SHUTDOWN"
         */
        public int getState()
        {
            return serverState;
        }

            /**
             * Retrieves a character sequence describing this server's current state,
             * including the message of the last exception, if there is one and it
             * is still _in context.
             *
             * @return this server's state represented as a character sequence.
             *
             * @jmx.managed-attribute
             *  access="read-only"
             *  description="State as string"
             */
            public string getStateDescriptor() {

                string    state;
                Exception t = getServerError();

                switch (serverState) {

                    case ServerConstants.SERVER_STATE_SHUTDOWN :
                        state = "SHUTDOWN";
                        break;

                    case ServerConstants.SERVER_STATE_OPENING :
                        state = "OPENING";
                        break;

                    case ServerConstants.SERVER_STATE_CLOSING :
                        state = "CLOSING";
                        break;

                    case ServerConstants.SERVER_STATE_ONLINE :
                        state = "ONLINE";
                        break;

                    default :
                        state = "UNKNOWN";
                        break;
                }

                return state;
            }

            /**
             * Retrieves the root context (directory) from which web content
             * is served.  This property is relevant only when the server
             * protocol is HTTP(S).  Although unlikely, it may be that _in the future
             * other contexts, such as jar urls may be supported, so that pages can
             * be served from the contents of a jar or from the JVM class path.
             *
             * @return the root context (directory) from which web content is served
             *
             * @jmx.managed-attribute
             *  access="read-write"
             *  description="Context (directory)"
             */
            public virtual string getWebRoot() {
                return "[IGNORED]";
            }

        /**
         * Assigns the specified socket to a new conection handler and
         * starts the handler _in a new Thread.
         *
         * @param s the socket to connect
         */
            public void handleConnection(TcpClient s)
            {

                Thread t = null;
                ThreadStart r = null;
                string ctn = null;

                printWithThread("handleConnection(" + s + ") entered");

                s.ReceiveTimeout = 1000;
                s.SendTimeout = 1000;
                s.NoDelay = true;

                if (!allowConnection(s))
                {
                    try
                    {
                        s.Close();
                    }
                    catch (Exception) { }

                    printWithThread("allowConnection(): connection refused");
                    printWithThread("handleConnection() exited");

                    return;
                }

                // Maybe set up socket options, SSL
                // Session tracing/callbacks, etc.
                //if (socketFactory != null) {
                //    socketFactory.configureSocket(s);
                //}

                if (serverProtocol == ServerConstants.SC_PROTOCOL_EFZ)
                {
                    ServerConnection sc = new ServerConnection(s, this);
                    ctn = sc.getConnectionThreadName();

                    lock (serverConnSet)
                    {
                        serverConnSet.Add(sc);
                    }

                    r = new ThreadStart(sc.run);
                }
                else
                {
                    WebServerConnection wconn = new WebServerConnection(s, (WebServer)this);
                    r = new ThreadStart(wconn.run);
                    ctn = wconn.getConnectionThreadName();
                }

                t = new Thread(r);
                t.Name = ctn;
                serverConnectionThreadGroup.Add(t);

                t.Start();
                printWithThread("handleConnection() exited");
            }

        /**
         * Retrieves whether this server calls System.exit() when shutdown.
         *
         * @return true if this server does not call System.exit()
         *
         * @jmx.managed-attribute
         *  access="read-write"
         *  description="When Shutdown"
         */
        public bool isNoSystemExit()
        {
            return serverProperties.isPropertyTrue(
                ServerConstants.SC_KEY_NO_SYSTEM_EXIT);
        }

            /**
             * Retrieves whether this server restarts on shutdown.
             *
             * @return true this server restarts on shutdown
             *
             * @jmx.managed-attribute
             *  access="read-write"
             *  description="Automatically?"
             */
            public bool isRestartOnShutdown() {
                return serverProperties.isPropertyTrue(
                    ServerConstants.SC_KEY_AUTORESTART_SERVER);
            }

        /**
         * Retrieves whether silent mode operation was requested in
         * the server properties.
         *
         * @return if true, silent mode was requested, else trace messages
         *      are to be printed
         *
         * @jmx.managed-attribute
         *  access="read-write"
         *  description="No trace messages?"
         */
        public bool isSilent()
        {
            return _isSilent;
        }

        /**
         * Retrieves whether the use of secure sockets was requested _in the
         * server properties.
         *
         * @return if true, secure sockets are requested, else not
         *
         * @jmx.managed-attribute
         *  access="read-write"
         *  description="Use TLS/SSL sockets?"
         */
        public bool isTls()
        {
            return serverProperties.isPropertyTrue(ServerConstants.SC_KEY_TLS);
        }

            /**
             * Retrieves whether JDBC trace messages are to go to System.out or the
             * DriverManger PrintStream/PrintWriter, if any.
             *
             * @return true if tracing is on (JDBC trace messages to system _out)
             *
             * @jmx.managed-attribute
             *  access="read-write"
             *  description="JDBC trace messages to System.out?"
             */
            public bool isTrace() {
                return serverProperties.isPropertyTrue(ServerConstants.SC_KEY_TRACE);
            }

            /**
             * Attempts to put properties from the file
             * with the specified path. The file
             * extension '.properties' is implicit and should not
             * be included _in the path specification.
             *
             * @param path the path of the desired properties file, without the
             *      '.properties' file extension
             * @  if this server is running
             * @return true if the indicated file was read sucessfully, else false
             *
             * @jmx.managed-operation
             *  impact="ACTION"
             *  description="Reads _in properties"
             *
             * @jmx.managed-operation-parameter
             *   name="path"
             *   type="string"
             *   position="0"
             *   description="(optional) returns false if path is empty"
             */
            public bool putPropertiesFromFile(string path) {

                if (getState() != ServerConstants.SERVER_STATE_SHUTDOWN) {
                    throw new Exception();
                }

                path = FileUtil.GetDefaultInstance().CanonicalOrAbsolutePath(path);

                EffiProzProperties p = ServerConfiguration.getPropertiesFromFile(path);

                if (p == null || p.isEmpty()) {
                    return false;
                }

                printWithThread("putPropertiesFromFile(): [" + path + ".properties]");

                try {
                    setProperties(p);
                } catch (Exception e) {
                    throw new Exception("Failed to set properties: " + e);
                }

                return true;
            }

            /**
             * Puts properties from the supplied string argument.  The relevant
             * key value pairs are the same as those for the (web)server.properties
             * file format, except that the 'server.' prefix should not be specified.
             *
             * @param s semicolon-delimited key=value pair string,
             *      e.g. k1=v1;k2=v2;k3=v3...
             * @  if this server is running
             *
             * @jmx.managed-operation
             *   impact="ACTION"
             *   description="'server.' key prefix automatically supplied"
             *
             * @jmx.managed-operation-parameter
             *   name="s"
             *   type="string"
             *   position="0"
             *   description="semicolon-delimited key=value pairs"
             */
            public void putPropertiesFromString(string s) {

                if (getState() != ServerConstants.SERVER_STATE_SHUTDOWN) {
                    throw new Exception();
                }

                if (String.IsNullOrEmpty(s)) {
                    return;
                }

                printWithThread("putPropertiesFromString(): [" + s + "]");

                EffiProzProperties p = EffiProzProperties.delimitedArgPairsToProps(s, "=",
                    ";", ServerConstants.SC_KEY_PREFIX);

                try {
                    setProperties(p);
                } catch (Exception e) {
                    throw new Exception("Failed to set properties: " + e);
                }
            }

            /**
             * Sets the InetAddress with which this server's ServerSocket will be
             * constructed.  A null or empty string or the special value "0.0.0.0"
             * can be used to bypass explicit selection, causing the ServerSocket
             * to be constructed without specifying an InetAddress.
             *
             * @param address A string representing the desired InetAddress as would
             *    be retrieved by InetAddres.getByName(), or a null or empty string
             *    or "0.0.0.0" to signify that the server socket should be constructed
             *    using the signature that does not specify the InetAddress.
             * @  if this server is running
             *
             * @jmx.managed-attribute
             */
            public void setAddress(string address)   {

                checkRunning(false);

                if (String.IsNullOrEmpty(address)) {
                    address = ServerConstants.SC_DEFAULT_ADDRESS;
                }

                printWithThread("setAddress(" + address + ")");
                serverProperties.setProperty(ServerConstants.SC_KEY_ADDRESS, address);
            }

            /**
             * Sets the external name (url alias) of the i'th hosted database.
             *
             * @param name external name (url alias) of the i'th HSQLDB database
             *      instance this server is to host.
             * @  if this server is running
             *
             * @jmx.managed-operation
             *      impact="ACTION"
             *      description="Sets the url alias by which is known the i'th hosted Database"
             *
             * @jmx.managed-operation-parameter
             *      name="index"
             *      type="int"
             *      position="0"
             *      description="This Server's index for the hosted Database"
             *
             * @jmx.managed-operation-parameter
             *      name="name"
             *      type="string"
             *      position="1"
             *      description="url alias component for the hosted Database"
             */
            public void setDatabaseName(int index,
                                        string name)   {

                checkRunning(false);
                printWithThread("setDatabaseName(" + index + "," + name + ")");
                serverProperties.setProperty(ServerConstants.SC_KEY_DBNAME + "."
                                             + index, name);
            }

            /**
             * Sets the path of the hosted database.
             *
             * @param path The path of the i'th HSQLDB database instance this server
             *      is to host.
             *
             * @jmx.managed-operation
             *      impact="ACTION"
             *      description="Sets the database uri path for the i'th hosted Database"
             *
             * @jmx.managed-operation-parameter
             *      name="index"
             *      type="int"
             *      position="0"
             *      description="This Server's index for the hosted Database"
             *
             * @jmx.managed-operation-parameter
             *      name="path"
             *      type="string"
             *      position="1"
             *      description="database uri path of the hosted Database"
             */
            public void setDatabasePath(int index,
                                        string path)   {

                checkRunning(false);
                printWithThread("setDatabasePath(" + index + "," + path + ")");
                serverProperties.setProperty(ServerConstants.SC_KEY_DATABASE + "."
                                             + index, path);
            }

            /**
             * Sets the name of the web page served when no page is specified.
             *
             * @param file the name of the web page served when no page is specified
             *
             * @jmx.managed-attribute
             */
            public void setDefaultWebPage(string file) {

                checkRunning(false);
                printWithThread("setDefaultWebPage(" + file + ")");

                if (serverProtocol != ServerConstants.SC_PROTOCOL_HTTP) {
                    return;
                }

                serverProperties.setProperty(ServerConstants.SC_KEY_WEB_DEFAULT_PAGE,
                                             file);
            }

            /**
             * Sets the server listen port.
             *
             * @param port the port at which this server listens
             *
             * @jmx.managed-attribute
             */
            public void setPort(int port)   {

                checkRunning(false);
                printWithThread("setPort(" + port + ")");
                serverProperties.setProperty(ServerConstants.SC_KEY_PORT, port);
            }

        /**
         * Sets the PrintWriter to which server errors are logged. <p>
         *
         * Setting this attribute to null disables server error logging
         *
         * @param pw the PrintWriter to which server messages are logged
         */
        public void setErrWriter(TextWriter pw)
        {           
            errWriter = pw;
        }

        /**
         * Sets the PrintWriter to which server messages are logged. <p>
         *
         * Setting this attribute to null disables server message logging
         *
         * @param pw the PrintWriter to which server messages are logged
         */
        public void setLogWriter(TextWriter pw)
        {
            logWriter = pw;
        }

            /**
             * Sets whether this server calls System.exit() when shutdown.
             *
             * @param noExit if true, System.exit() will not be called.
             *
             * @jmx.managed-attribute
             */
            public void setNoSystemExit(bool noExit) {

                printWithThread("setNoSystemExit(" + noExit + ")");
                serverProperties.setProperty(ServerConstants.SC_KEY_NO_SYSTEM_EXIT,
                                             noExit);
            }

            /**
             * Sets whether this server restarts on shutdown.
             *
             * @param restart if true, this server restarts on shutdown
             *
             * @jmx.managed-attribute
             */
            public void setRestartOnShutdown(bool restart) {

                printWithThread("setRestartOnShutdown(" + restart + ")");
                serverProperties.setProperty(ServerConstants.SC_KEY_AUTORESTART_SERVER,
                                             restart);
            }

            /**
             * Sets silent mode operation
             *
             * @param silent if true, then silent mode, else trace messages
             *  are to be printed
             *
             * @jmx.managed-attribute
             */
            public void setSilent(bool silent) {

                printWithThread("setSilent(" + silent + ")");
                serverProperties.setProperty(ServerConstants.SC_KEY_SILENT, silent);

                _isSilent = silent;
            }

            /**
             * Sets whether to use secure sockets
             *
             * @param tls true for secure sockets, else false
             * @  if this server is running
             *
             * @jmx.managed-attribute
             */
            public void setTls(bool tls) {

                checkRunning(false);
                printWithThread("setTls(" + tls + ")");
                serverProperties.setProperty(ServerConstants.SC_KEY_TLS, tls);
            }

            /**
             * Sets whether trace messages go to System.out or the
             * DriverManger PrintStream/PrintWriter, if any.
             *
             * @param trace if true, route JDBC trace messages to System.out
             *
             * @jmx.managed-attribute
             */
            public void setTrace(bool trace) {

                printWithThread("setTrace(" + trace + ")");
                serverProperties.setProperty(ServerConstants.SC_KEY_TRACE, trace);
                
            }

            /**
             * Sets the path of the root directory from which web content is served.
             *
             * @param root the root (context) directory from which web content
             *      is served
             *
             * @jmx.managed-attribute
             */
            public void setWebRoot(string root) {

                checkRunning(false);

                root = (new FileInfo(root)).FullName;

                printWithThread("setWebRoot(" + root + ")");

                if (serverProtocol != ServerConstants.SC_PROTOCOL_HTTP) {
                    return;
                }

                serverProperties.setProperty(ServerConstants.SC_KEY_WEB_ROOT, root);
            }

        /**
         * Sets server properties using the specified properties object
         *
         * @param p The object containing properties to set
         */
            public void setProperties(EffiProzProperties p)
        {

            checkRunning(false);

            if (p != null)
            {
                serverProperties.addProperties(p);
                ServerConfiguration.translateAddressProperty(serverProperties);
            }

            maxConnections = serverProperties.getIntegerProperty(
                ServerConstants.SC_KEY_MAX_CONNECTIONS, 16);

            //JavaSystem.setLogToSystem(isTrace());

            _isSilent =
                serverProperties.isPropertyTrue(ServerConstants.SC_KEY_SILENT);
            isRemoteOpen = serverProperties.isPropertyTrue(
                ServerConstants.SC_KEY_REMOTE_OPEN_DB);
        }

        /**
         * Starts this server synchronously. <p>
         *
         * This method waits for current state to change from
         * SERVER_STATE_OPENNING. In order to discover the success or failure
         * of this operation, server state must be polled or a subclass of Server
         * must be used that overrides the setState method to provide state
         * change notification.
         *
         * @return the server state noted at entry to this method
         *
         * @jmx.managed-operation
         *  impact="ACTION_INFO"
         *  description="Invokes asynchronous startup sequence; returns previous state"
         */
        public int start()
        {

            printWithThread("start() entered");

            int previousState = getState();

            if (serverThread != null)
            {
                printWithThread("start(): serverThread != null; no action taken");

                return previousState;
            }

            setState(ServerConstants.SERVER_STATE_OPENING);

            serverThread = new Thread(new ThreadStart(ServerThreadRun));
            serverThread.Name = "EffiProz Server " + '@' + string.Format("{0:x2}", this.GetHashCode());
            serverThread.Start();

            // call synchronized getState() to become owner of the Server Object's monitor
            while (getState() == ServerConstants.SERVER_STATE_OPENING)
            {
                try
                {
                    Thread.Sleep(100);
                }
                catch (ThreadInterruptedException) { }
            }

            printWithThread("start() exiting");

            return previousState;
        }

            /**
             * Stops this server asynchronously. <p>
             *
             * This method returns immediately, regardless of current state.  In order
             * to discover the success or failure of this operation, server state must
             * be polled or a subclass of Server must be used that overrides the
             * setState method to provide state change notification.
             *
             * @return the server state noted at entry to this method
             *
             * @jmx.managed-operation
             *  impact="ACTION_INFO"
             *  description="Invokes asynchronous shutdown sequence; returns previous state"
             */
            public int stop() {

                printWithThread("stop() entered");

                int previousState = getState();

                if (serverThread == null) {
                    printWithThread("stop() serverThread is null; no action taken");

                    return previousState;
                }

                releaseServerSocket();
                printWithThread("stop() exiting");

                return previousState;
            }

        /**
         * Retrieves whether the specified socket should be allowed
         * to make a connection.  By default, this method always returns
         * true, but it can be overidden to implement hosts allow-deny
         * functionality.
         *
         * @param socket the socket to test.
         */
        protected bool allowConnection(TcpClient socket)
        {
            return true;
        }

        /**
         * Initializes this server, setting the accepted connection protocol.
         *
         * @param protocol typically either SC_PROTOCOL_HTTP or SC_PROTOCOL_HSQL
         */
        protected void init(int protocol) {

                // PRE:  This method is only called from the constructor
                serverState      = ServerConstants.SERVER_STATE_SHUTDOWN;
                serverConnSet    = new HashSet<ServerConnection>();
                serverId         = ToString();
                serverId         = serverId.Substring(serverId.LastIndexOf('.') + 1);
                serverProtocol   = protocol;
                serverProperties = ServerConfiguration.newDefaultProperties(protocol);
                logWriter        =  (TextWriter)Console.Out;
                errWriter        = (TextWriter)Console.Error;

               
            }

            /**
             * Sets the server state value.
             *
             * @param state the new value
             */
            public  void setState(int state) {
                lock (this)
                {
                    serverState = state;
                }
            }

            /**
             * This is called from org.hsqldb.DatabaseManager when a database is
             * shutdown. This shuts the server down if it is the last database
             *
             * @param action a code indicating what has happend
             */
            public void Notify(ServerNotificationAction action, int id)
            {

                printWithThread("notifiy(" + action + "," + id + ") entered");

                if (action != ServerConstants.SC_DATABASE_SHUTDOWN) {
                    return;
                }

                releaseDatabase(id);

                bool shutdown = true;

                for (int i = 0; i < dbID.Length; i++)
                {
                    if (dbAlias[i] != null)
                    {
                        shutdown = false;
                    }
                }

                if (!isRemoteOpen && shutdown)
                {
                    stop();
                }
            }

            /**
             * This releases the resources used for a database.
             * Is called with id 0 multiple times for non-existent databases
             */
             void releaseDatabase(int id) {
             lock(this){
  
                printWithThread("releaseDatabase(" + id + ") entered");

                // check all slots as a database may be opened by multiple aliases
                for (int i = 0; i < dbID.Length; i++) {
                    if (dbID[i] == id && dbAlias[i] != null) {
                        dbID[i]    = 0;
                        dbAlias[i] = null;
                        dbPath[i]  = null;
                        dbType[i]  = null;
                        dbProps[i] = null;
                    }
                }
                
                ServerConnection[] it;
                lock (serverConnSet) {
                    it = new ServerConnection[serverConnSet.Count];
                    serverConnSet.CopyTo(it);
                }

                foreach (var sc in it) {

                    if (sc.dbID == id)
                    {  //TODOx
                        sc.signalClose();
                        serverConnSet.Remove(sc);
                    }
                }
                printWithThread("releaseDatabase(" + id + ") exiting");
             }
            }

        /**
         * Prints the specified message, s, formatted to identify that the print
         * operation is against this server instance.
         *
         * @param msg The message to print
         */
        public void print(string msg)
        {

            TextWriter writer = logWriter;

            if (writer != null)
            {
                writer.WriteLine("[" + serverId + "]: " + msg);
                writer.Flush();
            }
        }

  

        /**
         * Prints the stack trace of the Exception, t, to this Server object's
         * errWriter. <p>
         *
         * @param t the Exception whose stack trace is to be printed
         */
        public void printStackTrace(Exception t)
        {

            if (errWriter != null)
            {
                errWriter.WriteLine(t.StackTrace);              
                errWriter.Flush();
            }
        }

        /**
         * Prints the specified message, s, prepended with a timestamp representing
         * the current date and time, formatted to identify that the print
         * operation is against this server instance.
         *
         * @param msg the message to print
         */
        void printWithTimestamp(string msg)
        {
            print(DateTime.Now + " " + msg); 
        }

        /**
         * Prints a message formatted similarly to print(String), additionally
         * identifying the current (calling) thread. Replaces old method
         * trace(string msg).
         *
         * @param msg the message to print
         */
        public void printWithThread(string msg)
        {

            if (!isSilent())
            {
                print("[" + Thread.CurrentThread.ToString() + "]: " + msg);
            }
        }

        /**
         * Prints an error message to this Server object's errWriter.
         * The message is formatted similarly to print(String),
         * additionally identifying the current (calling) thread.
         *
         * @param msg the message to print
         */
        public void printError(string msg)
        {

            TextWriter writer = errWriter;

            if (writer != null)
            {
                writer.Write("[" + serverId + "]: ");
                writer.Write("[" + Thread.CurrentThread.ToString() + "]: ");
                writer.WriteLine(msg);
                writer.Flush();
            }
        }

            /**
             * Prints a description of the request encapsulated by the
             * Result argument, r.
             *
             * Printing occurs iff isSilent() is false. <p>
             *
             * The message is formatted similarly to print(String), additionally
             * indicating the connection identifier.  <p>
             *
             * For Server instances, cid is typically the value assigned to each
             * ServerConnection object that is unique amongst all such identifiers
             * _in each distinct JVM session / class loader
             * context. <p>
             *
             * For WebServer instances, a single logical connection actually spawns
             * a new physical WebServerConnection object for each request, so the
             * cid is typically the underlying session id, since that does not
             * change for the duration of the logical connection.
             *
             * @param cid the connection identifier
             * @param r the request whose description is to be printed
             */
            public void printRequest(int cid, Result r) {

                if (isSilent()) {
                    return;
                }

                StringBuilder sb = new StringBuilder();

                sb.Append(cid);
                sb.Append(':');

                switch (r.getType()) {

                    case ResultConstants.PREPARE : {
                        sb.Append("SQLCLI:SQLPREPARE ");
                        sb.Append(r.getMainString());

                        break;
                    }
                    case ResultConstants.EXECDIRECT : {
                        sb.Append(r.getMainString());

                        break;
                    }
                    case ResultConstants.EXECUTE : {
                        sb.Append("SQLCLI:SQLEXECUTE:");

                        if (r.getNavigator().getSize() > 1) {
                            sb.Append("BATCHMODE:");
                        }

                        sb.Append(r.getStatementID());

        /**
         * todo - fredt - NOW - fix this without appendStringValueOf
         */
        /*
                        if (r.getSize() == 1) {
                            sb.Append('\n');
                            StringUtil.AppendStringValueOf(r.getParameterData(), sb, true);
                        }
        */
                        break;
                    }
                    case ResultConstants.UPDATE_RESULT:
                    {
                            sb.Append("SQLCLI:RESULTUPDATE:");
                            sb.Append(r.getStatementID());

                            break;
                    }   
                    case ResultConstants.FREESTMT : {
                        sb.Append("SQLCLI:SQLFREESTMT:");
                        sb.Append(r.getStatementID());

                        break;
                    }
                    case ResultConstants.GETSESSIONATTR : {
                        sb.Append("EFZCLI:GETSESSIONATTR");

                        break;
                    }
                    case ResultConstants.SETSESSIONATTR : {
                        sb.Append("EFZCLI:SETSESSIONATTR:");
                        break;
                    }
                    case ResultConstants.ENDTRAN:
                        {
                            sb.Append("SQLCLI:SQLENDTRAN:");

                        switch (r.getActionType()) {

                            case ResultConstants.TX_COMMIT:
                                sb.Append("COMMIT");
                                break;

                            case ResultConstants.TX_ROLLBACK:
                                sb.Append("ROLLBACK");
                                break;

                            case ResultConstants.TX_SAVEPOINT_NAME_RELEASE:
                                sb.Append("SAVEPOINT_NAME_RELEASE ");
                                sb.Append(r.getMainString());
                                break;

                            case ResultConstants.TX_SAVEPOINT_NAME_ROLLBACK:
                                sb.Append("SAVEPOINT_NAME_ROLLBACK ");
                                sb.Append(r.getMainString());
                                break;

                            default :
                                sb.Append(r.getActionType());
                                break;
                        }

                        break;
                    }
                    case ResultConstants.STARTTRAN:
                        {
                        sb.Append("SQLCLI:SQLSTARTTRAN");

                        break;
                    }
                    case ResultConstants.DISCONNECT:
                        {
                        sb.Append("SQLCLI:SQLDISCONNECT");

                        break;
                    }
                    case ResultConstants.SETCONNECTATTR:
                        {
                        sb.Append("SQLCLI:SQLSETCONNECTATTR:");

                        switch (r.getConnectionAttrType()) {

                            case ResultConstants.SQL_ATTR_SAVEPOINT_NAME : {
                                sb.Append("SQL_ATTR_SAVEPOINT_NAME ");
                                sb.Append(r.getMainString());

                                break;
                            }
                            default : {
                                sb.Append(r.getConnectionAttrType());
                                break;
                            }
                        }

                        break;
                    }
                    default : {
                        sb.Append("SQLCLI:MODE:");
                        sb.Append(r.getType());

                        break;
                    }
                }

                print(sb.ToString());
            }

            /**
             * return database ID
             */
            public int getDBIndex(string aliasPath)
            {

                lock (this)
                {
                    int semipos = aliasPath.IndexOf(';');
                    string alias = aliasPath;
                    string filepath = null;

                    if (semipos != -1)
                    {
                        alias = aliasPath.Substring(0, semipos - (0));
                        filepath = aliasPath.Substring(semipos + 1);
                    }

                    int dbIndex = ArrayUtil.find(dbAlias, alias);

                    if (dbIndex == -1)
                    {
                        if (filepath == null)
                        {
                            Exception e =
                                new Exception("database alias does not exist");

                            printError("database alias=" + alias + " does not exist");
                            setServerError(e);

                            throw e;
                        }
                        else
                        {
                            return openDatabase(alias, filepath);
                        }
                    }
                    else
                    {
                        return dbIndex;
                    }
                }
            }

            /**
             * Open and return database ID
             */
           public int openDatabase(string alias,
                                   string filepath)   {

                if (!isRemoteOpen) {
                    Exception e =
                        new Exception("remote open not allowed");

                    printError("Remote database open not allowed");
                    setServerError(e);

                    throw e;
                }

                int i = getFirstEmptyDatabaseIndex();

                if (i < -1) {
                   
                        Exception e =
                            new Exception("limit of open databases reached");

                        printError("limit of open databases reached");
                        setServerError(e);

                        throw e;
               
                }

                EffiProzProperties newprops = DatabaseURL.parseURL(filepath, false);

                if (newprops == null) {
                    Exception e = new Exception("invalid database path");

                    printError("invalid database path");
                    setServerError(e);

                    throw e;
                }

                string path = newprops.getProperty("database");
                string type = newprops.getProperty("connection_type");

                try {
                    int dbid = DatabaseManager.getDatabase(type, path, this, newprops);

                    dbID[i]    = dbid;
                    dbActionSequence[i] = actionSequence;
                    dbAlias[i] = alias;
                    dbPath[i]  = path;
                    dbType[i]  = type;
                    dbProps[i] = newprops;

                    return dbid;
                } catch (CoreException e) {
                    printError("Database [index=" + i + "db=" + dbType[i] + dbPath[i]
                               + ", alias=" + dbAlias[i] + "] did not open: "
                               + e.ToString());
                    setServerError(e);

                    throw e;
                }
            }

            public int getFirstEmptyDatabaseIndex() {

                for (int i = 0; i < dbAlias.Length; i++) {
                    if (dbAlias[i] == null) {
                        return i;
                    }
                }

                return -1;
            }

            /**
             * Opens this server's database instances. This method returns true If
             * at least one database goes online, otherwise it returns false.
             *
             * If openning any of the databases is attempted and an exception is
             * thrown, the server error is set to this exception.
             *
             * @  if a database access error occurs
             */
            public bool openDatabases() {

                printWithThread("openDatabases() entered");

                bool success = false;

                setDBInfoArrays();

                for (int i = 0; i < dbAlias.Length; i++) {
                    if (dbAlias[i] == null) {
                        continue;
                    }

                    printWithThread("Opening database: [" + dbType[i] + dbPath[i]
                                    + "]");

                    StopWatch sw = new StopWatch();
                    int       id =0;

                    try {
                        id = DatabaseManager.getDatabase(dbType[i], dbPath[i], this,
                                                         dbProps[i]);   //TODOx
                        dbID[i] = id;
                        success = true;
                    } catch (CoreException e) {
                        printError("Database [index=" + i + "db=" + dbType[i]
                                   + dbPath[i] + ", alias=" + dbAlias[i]
                                   + "] did not open: " + e.ToString());
                        setServerError(e);

                        dbAlias[i] = null;
                        dbPath[i]  = null;
                        dbType[i]  = null;
                        dbProps[i] = null;

                        continue;
                    }

                    sw.stop();

                    string msg = "Database [index=" + i + ", id=" + id + ", " + "db="
                                 + dbType[i] + dbPath[i] + ", alias=" + dbAlias[i]
                                 + "] opened sucessfully";

                    print(sw.elapsedTimeToMessage(msg));
                }

                printWithThread("openDatabases() exiting");

                if (isRemoteOpen) {
                    success = true;
                }

                if (!success && getServerError() == null) {

                    // database alias / path list is empty or without full info for any DB
                    setServerError(Error.error(ErrorCode.SERVER_NO_DATABASE));
                }

                return success;
            }

             /**
              * Initialises the database attributes lists from the server properties object.
              */
             private void setDBInfoArrays()
             {
                 IntKeyHashMap dbNumberMap = getDBNameArray();
                 int maxDatabases = dbNumberMap.size();

                 if (serverProperties.isPropertyTrue(
                        ServerConstants.SC_KEY_REMOTE_OPEN_DB))
                 {
                     int max = serverProperties.getIntegerProperty(
                         ServerConstants.SC_KEY_MAX_DATABASES,
                         ServerConstants.SC_DEFAULT_MAX_DATABASES);

                     if (maxDatabases < max)
                     {
                         maxDatabases = max;
                     }
                 }

                 dbAlias = new String[maxDatabases];
                 dbPath = new String[dbAlias.Length];
                 dbType = new String[dbAlias.Length];
                 dbID = new int[dbAlias.Length];
                 dbActionSequence = new long[dbAlias.Length];
                 dbProps = new EffiProzProperties[dbAlias.Length];

                 Iterator it = dbNumberMap.keySet().iterator();

                 for (int i = 0; it.hasNext();)
                 {
                     int dbNumber = it.nextInt();
                     String path = getDatabasePath(dbNumber, true);

                     if (path == null)
                     {
                         printWithThread("missing database path: "
                                         + dbNumberMap.get(dbNumber));

                         continue;
                     }

                     EffiProzProperties dbURL = DatabaseURL.parseURL(path, false);

                     if (dbURL == null)
                     {
                         printWithThread("malformed database path: " + path);

                         continue;
                     }

                     dbAlias[i] = (String)dbNumberMap.get(dbNumber);
                     dbPath[i] = dbURL.getProperty("database");
                     dbType[i] = dbURL.getProperty("connection_type");
                     dbProps[i] = dbURL;

                     i++;
                 }
             }

             /**
              * Returns a possibly sparse array of all server.dbname.n values
              * from the properties object.
              */
             private IntKeyHashMap getDBNameArray()
             {

                 string prefix = ServerConstants.SC_KEY_DBNAME + ".";
                 int prefixLen = prefix.Length;               
                 IntKeyHashMap idToAliasMap = new IntKeyHashMap();
                
                 try
                 {           

                     foreach (string key in serverProperties.propertyNames())
                     {                         
                         if (!key.StartsWith(prefix))
                         {
                             continue;
                         }

                         try
                         {
                             int dbnum ;

                             try
                             {
                                 dbnum = int.Parse(key.Substring(prefixLen));
                             }
                             catch (FormatException)
                             {
                                 printWithThread("maformed database enumerator: " + key);

                                 continue;
                             }
                            
                             String alias =
                                 serverProperties.getProperty(key).ToLower();

                             if (!aliasSet.add(alias))
                             {
                                 printWithThread("duplicate alias: " + alias);
                             }

                             Object existing = idToAliasMap.put(dbnum, alias);

                             if (existing != null)
                             {
                                 printWithThread("duplicate database enumerator: " + key);
                             }
                         }
                         catch (FormatException e)
                         {
                             printWithThread("dblist: " + e.ToString());
                         }
                     }
                 }
                 catch (Exception e) 
                 {
                     printWithThread("dblist: " + e.ToString());
                 }


                 return idToAliasMap;
             }

        /**
         * Constructs and installs a new ServerSocket instance for this server.
         *
         * @  if it is not possible to construct and install
         *      a new ServerSocket
         */
        private void openServerSocket()
        {

            string address;
            int port;
            String[] candidateAddrs;   
            StopWatch sw;

            printWithThread("openServerSocket() entered");

            if (isTls())
            {
                printWithThread("Requesting TLS/SSL-encrypted ADO");
            }

            sw = new StopWatch();

            address = getAddress();
            port = getPort();

            if (string.IsNullOrEmpty(address)
                    || ServerConstants.SC_DEFAULT_ADDRESS.Equals(
                        address.Trim(), StringComparison.InvariantCultureIgnoreCase))
            {
                listner = new TcpListener(IPAddress.Any, port);
            }
            else
            {
                try
                {
                    listner = new TcpListener(IPAddress.Parse(address), port);
                }
                catch (SocketException)
                {
                    candidateAddrs =
                        ServerConfiguration.listLocalInetAddressNames();

                    int messageID;
                    Object[] messageParameters;

                    if (candidateAddrs.Length > 0)
                    {
                        messageID = ErrorCode.M_SERVER_OPEN_SERVER_SOCKET_1;
                        messageParameters = new Object[] {
                                address, candidateAddrs
                            };
                    }
                    else
                    {
                        messageID = ErrorCode.M_SERVER_OPEN_SERVER_SOCKET_2;
                        messageParameters = new Object[] { address };
                    }

                    throw new Exception(Error.getMessage(messageID,
                            0, messageParameters));
                }
            }

            /*
             * Following line necessary for Java 1.3 on UNIX.  See accept()
             * comment elsewhere _in this file.
             */
            //socket.ReceiveTimeout = 1000;
            //socket.SendTimeout = 1000;
            printWithThread("Got server socket: " + listner);
            print(sw.elapsedTimeToMessage("Server socket opened successfully"));

            if (isTls())
            {
                print("Using TLS/SSL-encrypted ");
            }

            printWithThread("openServerSocket() exiting");
        }

        /** Prints a timestamped message indicating that this server is online */
        private void printServerOnlineMessage()
        {

            string s = getProductName() + " " + getProductVersion() + " is online";

            printWithTimestamp(s);         
        }

            /**
             * Prints a description of the server properties iff !isSilent().
             */
            protected void printProperties() {                
            
                string      value;

                // Avoid the waste of generating each description,
                // only for trace() to silently discard it
                if (isSilent()) {
                    return;
                }
              
                foreach (var key in serverProperties.propertyNames())
                {                   
                    value = serverProperties.getProperty(key);
                    printWithThread(key + "=" + value);
                }
            }

            /**
             * Puts this server into the SERVER_CLOSING state, closes the ServerSocket
             * and nullifies the reference to it. If the ServerSocket is already null,
             * this method exists immediately, otherwise, the result is to fully
             * shut down the server.
             */
            private void releaseServerSocket()
            {

                printWithThread("releaseServerSocket() entered");

                if (listner != null)
                {
                    printWithThread("Releasing server socket: [" + listner + "]");
                    setState(ServerConstants.SERVER_STATE_CLOSING);

                    try
                    {
                        listner.Stop();
                    }
                    catch (IOException e)
                    {
                        printError("Exception closing server socket");
                        printError("releaseServerSocket(): " + e);
                    }

                    listner = null;
                }

                printWithThread("releaseServerSocket() exited");
            }

        /**
         * Attempts to bring this server fully online by opening
         * a new ServerSocket, obtaining the hosted databases,
         * notifying the status waiter thread (if any) and
         * finally entering the listen loop if all else succeeds.
         * If any part of the process fails, then this server enters
         * its shutdown sequence.
         */
        private void run()
        {

            StopWatch sw;           
            string tgName;          

            printWithThread("run() entered");
            print("Initiating startup sequence...");
            printProperties();

            sw = new StopWatch();

            setServerError(null);

            try
            {
                // Faster init first:
                // It is huge waste to fully open the databases, only
                // to find that the socket address is already _in use
                openServerSocket();
            }
            catch (Exception e)
            {
                setServerError(e);
                printError("run()/openServerSocket(): ");
                printStackTrace(e);
                shutdown(true); 

                return;
            }

            tgName = "EffiProz Connections @" + string.Format("{0:X}", this.GetHashCode());       

            serverConnectionThreadGroup =  new List<Thread>();

            // Mount the databases this server is supposed to host.
            // This may take some time if the databases are not all
            // already open.
            if (openDatabases() == false)
            {
                setServerError(null);
                printError("Shutting down because there are no open databases");
                shutdown(true);

                return;
            }

            // At this point, we have a valid server socket and
            // a valid hosted database set, so its OK to start
            // listening for connections.
            setState(ServerConstants.SERVER_STATE_ONLINE);
            print(sw.elapsedTimeToMessage("Startup sequence completed"));
            printServerOnlineMessage();

            listner.Start();
            try
            {
                /*
                 * This loop is necessary for UNIX w/ Sun Java 1.3 because
                 * _in that case the socket.close() elsewhere will not
                 * interrupt this accept().
                 */
                while (true)
                {
                    handleConnection(listner.AcceptTcpClient());
                   
                }
            }
            catch (IOException e)
            {
                if (getState() == ServerConstants.SERVER_STATE_ONLINE)
                {
                    setServerError(e);
                    printError(this + ".run()/handleConnection(): ");
                    printStackTrace(e);
                }
            }
            catch (Exception t)
            {
                printWithThread(t.ToString());
            }
            finally
            {
                shutdown(false);    // or maybe getServerError() != null?
            }
        }

        /**
         * Sets this Server's last encountered error state.
         *
         * @param t The new value for the server error
         */
        protected void setServerError(Exception t)
        {
            serverError = t;
        }

        /**
         * External method to shut down this server.
         */
        public void shutdown()
        {
            shutdown(false);
        }

            /**
             * Shuts down this server.
             *
             * @param error true if shutdown is _in response to an error
             *      state, else false
             */
        protected void shutdown(bool error)
        {
            lock (this)
            {
                if (serverState == ServerConstants.SERVER_STATE_SHUTDOWN)
                {
                    return;
                }

                StopWatch sw;

                printWithThread("shutdown() entered");

                sw = new StopWatch();

                print("Initiating shutdown sequence...");
                releaseServerSocket();
                DatabaseManager.deRegisterServer(this); 

                if (dbPath != null)
                {
                    for (int i = 0; i < dbPath.Length; i++)
                    {
                        releaseDatabase(dbID[i]);
                    }
                }

                // Be nice and let applications exit if there are no
                // running connection threads
                if (serverConnectionThreadGroup != null)
                {
              
                        foreach (var t in serverConnectionThreadGroup)
                        {                       

                            try
                            {
                                Thread.Sleep(100);
                            }
                            catch (Exception )
                            {

                                // e.Message ;
                            }

                            try
                            {
                                if (t.IsAlive)
                                    t.Abort();
                                printWithThread(t.Name + " destroyed");
                            }
                            catch (Exception )
                            {
                                printWithThread(t.Name + " not destroyed");
                                printWithThread(t.ToString());
                            }
                        }               
                 
                   
                    serverConnectionThreadGroup = null;
                }

                serverThread = null;

                setState(ServerConstants.SERVER_STATE_SHUTDOWN);
                print(sw.elapsedTimeToMessage("Shutdown sequence completed"));

                if (isNoSystemExit())
                {
                    printWithTimestamp("SHUTDOWN : System.exit() was not called");
                    printWithThread("shutdown() exited");
                }
                else
                {
                    printWithTimestamp("SHUTDOWN : System.exit() is called next");
                    printWithThread("shutdown() exiting...");

                    try
                    {
                        Environment.Exit(0);                     
                    }
                    catch (Exception t)
                    {
                        printWithThread(t.ToString());
                    }
                }

            }
        }

        /**
         * Prints message for the specified key, without any special
         * formatting. The message content comes from the server
         * resource bundle and thus may localized according to the default
         * JVM locale.<p>
         *
         * Uses System.out directly instead of Trace.printSystemOut() so it
         * always prints, regardless of Trace settings.
         *
         * @param key for message
         */
        public static string getHelp(string key)
        {
            return BundleHandler.getString(serverBundleHandle, key);
       
        }    
    }
}
